package com.ivan.toolkit.common;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@AllArgsConstructor
@NoArgsConstructor
public class LngLat {
	
	/** 地球半径，单位:米 */
	public static final double EarthRadius = 6378137;
	
	/**经度*/
	@Setter @Getter
	private double lng;
	
	/**纬度*/
	@Setter @Getter
	private double lat;
	
	/**计算与另一点距离，单位：米*/
	public double distanceTo(LngLat lnglat) {
		return distanceTo(lnglat.getLng(), lnglat.getLat());
	}
	
	/**计算与另一点距离，单位：米*/
	public double distanceTo(double lng, double lat) {
		return distance(this.lng, this.lat, lng, lat);
	}
	
	/**计算与线段距离，单位：米，负数表示投影不在线段上*/
	public double distanceTo(LngLat lnglat1, LngLat lnglat2) {
		return distanceTo(lnglat1.getLng(), lnglat1.getLat(), lnglat2.getLng(), lnglat2.getLat());
	}
	
	/**计算与线段距离，单位：米，负数表示投影不在线段上*/
	public double distanceTo(double lng1, double lat1, double lng2, double lat2) {
		return distance(this.lng, this.lat, lng1, lat1, lng2, lat2);
	}
	
	
	/**
	 * 计算地球上任意两点(经纬度)距离
	 * @param long1	第一点经度
	 * @param lat1	第一点纬度
	 * @param long2	第二点经度
	 * @param lat2     第二点纬度
	 * @return 返回距离 单位：米
	 */
	public static double distance(
			double long1, 
			double lat1, 
			double long2,
			double lat2) {
		
		double a, b, R;
		R = EarthRadius; // 地球半径
		lat1 = lat1 * Math.PI / 180.0;
		lat2 = lat2 * Math.PI / 180.0;
		a = lat1 - lat2;
		b = (long1 - long2) * Math.PI / 180.0;
		
		double d;
		double sa2, sb2;
		sa2 = Math.sin(a / 2.0);
		sb2 = Math.sin(b / 2.0);
		d = 2
				* R
				* Math.asin(Math.sqrt(sa2 * sa2 + Math.cos(lat1)
						* Math.cos(lat2) * sb2 * sb2));
		return d;
	}
	
	
    /** 
     * 点到线段的距离 计算线段和计算点到直线的距离类似，
     * 不同Y点在包含l线段的投影可能不是线段上的点，
     * 投影可能在起点之前或者终点之后 ,这种情况返回负数。
     */  
    public static double distance(
    		double x, double y, 
    		double x1, double y1, 
    		double x2, double y2) {
    	
        double bc, ab, ac;  
        
        bc = distance(x1, y1, x2, y2);	// 线段的长度  
        ab = distance(x1, y1, x, y); 	// point1到点的距离  
        ac = distance(x2, y2, x, y);		//point2到点的距离  
        return  distance(bc, ab, ac);
    } 
	
    /** 
     * 点到线段的距离 计算线段和计算点到直线的距离类似，
     * 不同Y点在包含l线段的投影可能不是线段上的点，
     * 投影可能在起点之前或者终点之后 ,这种情况返回负数。
     */  
    public static double distance(
    		double bc, 
    		double ab, 
    		double ac) {
    	
        double dis = 0;  
        double a, b, c;  
        
        a = bc;	// 线段的长度  
        b = ab; 	// point1到点的距离  
        c = ac;		//point2到点的距离  
        if (c + b == a) {// 点在线段上  
            dis = 0;  
            return dis;  
        }  
        
        double a2 = a*a;
        double b2 = b*b;
        double c2 = c*c;
        
        if (c2 > a2 + b2) { // 组成直角三角形或钝角三角形，投影在point1延长线上，  
            dis = b;  
            return -dis;  
        }  
        
        if (c2 == a2 + b2) { // 组成直角三角形或钝角三角形，投影在point1延长线上，  
            dis = b;  
            return dis;  
        } 
        
        if (b2 > a2 + c2) {// 组成直角三角形或钝角三角形，投影在point2延长线上，  
            dis = c;  
            return -dis;  
        } 
        
        if (b2 == a2 + c2) {// 组成直角三角形或钝角三角形，投影在point2延长线上，  
            dis = c;  
            return dis;  
        }  
        
        // 组成锐角三角形，则求三角形的高  
        double p = (a + b + c) / 2;// 半周长  
        double s = Math.sqrt(p * (p - a) * (p - b) * (p - c));// 海伦公式求面积  
        dis = 2 * s / a;// 返回点到线的距离（利用三角形面积公式求高）  
        return dis;  
    } 
    
    public LngLat toMars() {
    	return Converter.transform2Mars(lng, lat);
    }
    
    public LngLat toBaidu() {
    	return Converter.bd_encrypt(lng, lat);
    }
    
    public static class Converter {
    	
    	static final double pi = 3.14159265358979324;
        static final double a = 6378245.0;  
        static final double ee = 0.00669342162296594323;  
        static final double x_pi = 3.14159265358979324 * 3000.0 / 180.0;

        public static boolean outOfChina(double lon, double lat) {
    		if (lon < 72.004 || lon > 137.8347)
    			return true;
    		if (lat < 0.8293 || lat > 55.8271)
    			return true;
    		return false;
    	}

        static double transformLat(double x, double y)
    	{
    		double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
    		ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
    		ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0;
    		ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
    		return ret;
    	}

        static double transformLon(double x, double y)
    	{
    		double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
    		ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0;
    		ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0;
    		ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0;
    		return ret;
    	}

        /**
         * 地球坐标转换为火星坐标
         * World Geodetic System ==> Mars Geodetic System
         *
         * @param wgLat  地球坐标
         * @param wgLon
         *
         * mglat,mglon 火星坐标
         */
        public static LngLat transform2Mars(double wgLon, double wgLat)
        {
            if (outOfChina(wgLon, wgLat))
                return new LngLat(wgLon, wgLat);
            
            
            double dLat = transformLat(wgLon - 105.0, wgLat - 35.0);
            double dLon = transformLon(wgLon - 105.0, wgLat - 35.0);
            double radLat = wgLat / 180.0 * pi;
            double magic = Math.sin(radLat);
            magic = 1 - ee * magic * magic;
            double sqrtMagic = Math.sqrt(magic);
            dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
            dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
            
            return new LngLat(wgLon + dLon, wgLat + dLat);

        }

        /**
         * 火星坐标转换为百度坐标
         * @param gg_lat
         * @param gg_lon
         */
        public static LngLat bd_encrypt(double gg_lon, double gg_lat)
        {
            double x = gg_lon, y = gg_lat;
            double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);
            double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi);
            double bd_lon = z * Math.cos(theta) + 0.0065;
            double bd_lat = z * Math.sin(theta) + 0.006;
            return new LngLat(bd_lon, bd_lat);
        }

        /**
         * 百度转火星
         * @param bd_lat
         * @param bd_lon
         */
        public static LngLat bd_decrypt(double bd_lon, double bd_lat)
        {
            double x = bd_lon - 0.0065, y = bd_lat - 0.006;
            double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
            double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
            double gg_lon = z * Math.cos(theta);
            double gg_lat = z * Math.sin(theta);
            return new LngLat(gg_lon, gg_lat);
        }
    	
    }

}
